package chagine.core.http.service;

import chagine.core.annotation.CommonResult;
import chagine.core.exception.ExceptionInfix;
import chagine.core.exception.SeeingException;
import chagine.core.exception.SeeingRuntimeException;
import chagine.core.restful.util.HResponse;
import chagine.core.restful.util.TResponseObject;
import chagine.core.util.ExceptionUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.Objects;
import java.util.Set;

/**
 * com.seeing.core.rpc.service.ExceptionHandlers
 * Created by 王彬安（wba）on 2018/5/24.
 */
@Slf4j
@RestControllerAdvice
@Order(ExceptionHandlers.ExceptionHandlerOrder)
public class ExceptionHandlers implements ResponseBodyAdvice<Object> {

    public static final int ExceptionHandlerOrder = Ordered.LOWEST_PRECEDENCE - 100;

    @Value("${rpc.service.exception-code-prefix:}")
    private String exceptionCodePrefix;

    @Resource
    private ObjectMapper objectMapper;

    private String getCode(Exception ex) {
        String codePrefix = this.exceptionCodePrefix;
        if (codePrefix == null) {
            codePrefix = "";
        }
        String code = ExceptionUtils.getSeeingCode(ex);
        if (code.length() <= 2) {
            code = codePrefix + ExceptionInfix.FILL_CODE + code;
        } else if (code.length() <= 4) {
            code = codePrefix + code;
        }
        return code;
    }

    /* 处理400类异常 */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public Object handleConstraintViolationException(ConstraintViolationException e) {
        StringBuilder sb = new StringBuilder();
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        int index = 1;
        for (ConstraintViolation<?> violation : violations) {
            sb.append(violation.getMessageTemplate());
            if (index != violations.size()) {
                sb.append(", ");
            }
            index++;
        }
        log.error("Service have exception, 参数验证失败 {}", sb.toString());
        return new HResponse<>(getCode(e), null, sb.toString());
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        FieldError error = bindingResult.getFieldError();
        StringBuilder sb = new StringBuilder();
        sb.append("参数: ").append(error.getField()).append(", ");
        sb.append(error.getDefaultMessage());
        // 生成返回结果
        log.error("Service method argument valid have exception {}", sb.toString());
        return new HResponse<>(getCode(e), null, sb.toString());
    }


    /**
     * 违反数据库唯一索引
     */
    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseStatus(HttpStatus.OK)
    public HResponse<Void> handleDuplicateKeyException(DuplicateKeyException ex) {
        log.error("DuplicateKeyException: {}", ExceptionUtils.exceptionStackTraceAsString(ex));
        return new HResponse<>(getCode(ex), null, "数据重复");
    }

    @ExceptionHandler(value = {SeeingException.class, SeeingRuntimeException.class})
    @ResponseStatus(HttpStatus.OK)
    public Object seeingExceptionExceptionHandler(Exception ex) {
        SeeingException exception = ExceptionUtils.getSeeingException(ex);
        if (exception != null) {
            ex = exception;
        }
        log.error("Service have exception {}", ExceptionUtils.exceptionStackTraceAsString(ex));
        return new HResponse<>(getCode(ex), null, ExceptionUtils.getCauseMessage(ex));
    }

    /**
     * 请求参数的绑定异常
     *
     * @param ex 异常内容
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object springBindExceptionHandler(BindException ex) {
        // ex.getFieldError():随机返回一个对象属性的异常信息。如果要一次性返回所有对象属性异常信息，则调用ex.getAllErrors()
        FieldError fieldError = ex.getFieldError();
        if (fieldError == null) {
            return serverExceptionHandler(ex);
        } else {
            StringBuilder sb = new StringBuilder();
            sb.append("参数: ").append(fieldError.getField()).append(", ");
            sb.append(fieldError.getDefaultMessage());
            // 生成返回结果
            log.error("Service Spring Bind have exception {}", sb.toString());
            return new HResponse<>(getCode(ex), null, sb.toString());
        }
    }

    @ExceptionHandler(DataAccessException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Object dataAccessEceptionHandler(Exception ex) {
        SeeingException exception = ExceptionUtils.getSeeingException(ex);
        if (exception != null) {
            ex = exception;
        }
        log.error("Service have exception {}", ExceptionUtils.exceptionStackTraceAsString(ex));
        return new HResponse<>(getCode(ex), null, "服务异常，请稍后再试");
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
//    @ResponseStatus(HttpStatus.OK)
    public Object serverExceptionHandler(Exception ex) {
        SeeingException exception = ExceptionUtils.getSeeingException(ex);
        if (exception != null) {
            ex = exception;
        }
        log.error("Service have exception {}", ExceptionUtils.exceptionStackTraceAsString(ex));
        return new HResponse<>(getCode(ex), null, ExceptionUtils.getCauseMessage(ex));
    }


    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) {
        // 方法上
        CommonResult methodAnnotation = methodParameter.getMethodAnnotation(CommonResult.class);
        boolean exist = Objects.nonNull(methodAnnotation);
        if (!exist) {
            // 类上
            CommonResult annotation = methodParameter.getDeclaringClass().getAnnotation(CommonResult.class);
            exist = Objects.nonNull(annotation);
        }
        return exist;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        TResponseObject response = TResponseObject.success(body);
        if (body instanceof String) {
            try {
                /*
                https://blog.csdn.net/weixin_33961829/article/details/92143322
                导致这个问题的原因就是，controller层中返回的类型是String，
                但是在ResponseBodyAdvice实现类中，我们把响应的类型修改成了ResponseResult。
                这就导致了，上面的这段代码在选择处理MessageConverter的时候，
                依旧根据之前的String类型选择对应String类型的StringMessageConverter。
                而在StringMessageConverter类型，他只接受String类型的返回类型，
                我们在ResponseBodyAdvice中将返回值从String类型改成ResponseResult类型之后，
                调用StringMessageConverter方法发生类型强转。
                ReponseResult无法转换成String，发生类型转换异常。
                 */
                HttpHeaders headers = serverHttpResponse.getHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return objectMapper.writeValueAsString(response);
            } catch (JsonProcessingException e) {
                return null;
            }
        }
        return response;
    }
}
